<!-- eslint-disable vue/multi-word-component-names no-unused-vars no-undef -->
<template>
  <div ref="sceneView" class="scene-view">
    <div v-if="!gameDivElement" id="GameDiv" cc_exact_fit_screen="false"
      style="width: 1920px; height: 1080px;border:1px solid red">
      <div id="Cocos3dGameContainer"></div>
    </div>
    <Loading v-if="showLoading && isLoading">{{ loadingContent }}</Loading>
  </div>
</template>

<script lang="ts" setup>
import {
  defineProps,
  defineEmits,
  ref,
  onMounted,
  defineExpose,
  onUnmounted,
  watch,
  onBeforeUnmount,
  inject,
} from "vue";
import {
  getDefaultScene,
  SceneEventType,
  scenes,
  Views,
  engine,
  SceneViewerConfigKey,
} from "./SceneViewer";
import type {
  EventMap,
  SceneInfo,
  EventDeviceInfo,
  SceneManager,
} from "./SceneViewer";

import { combinPath, isReady } from "./ScriptLoader";
import Loading from "./Loading.vue";
export interface SceneProps {
  sceneName?: string;
  showFPS?: boolean;
  debugMode?: string;
  assertPath?: string;
  showLoading?: boolean;
}

const props = withDefaults(defineProps<SceneProps>(), {
  sceneName: getDefaultScene().name,
  showFPS: false,
  showLoading: true,
});

const emits = defineEmits([
  "deviceSelected",
  "deviceUnselected",
  "sceneLoading",
  "sceneLoaded",
  "sceneLoadFailed",
]);

type EventTypes =
  | "deviceSelected"
  | "deviceUnselected"
  | "sceneLoading"
  | "sceneLoaded"
  | "sceneLoadFailed";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const callbacks: Record<string, any> = {};
// const engine = ref();
const curSceneInfo = ref<SceneInfo>();

const defaultView = ref<Views>(Views.lookdownView);
const sceneView = ref<HTMLElement>();
const gameDivElement = document.getElementById("GameDiv");
const sceneViewerConfig = inject(SceneViewerConfigKey, { serverPath: "" });
const loadingContent = ref("加载中...");
const isLoading = ref(true);

onMounted(async () => {
  if (gameDivElement) {
    if (gameDivElement.parentElement) {
      gameDivElement.parentElement.removeChild(gameDivElement);
    }
    gameDivElement.style.display = "block";
    if (sceneView.value) {
      sceneView.value.appendChild(gameDivElement);
    }
  } else {
    if (sceneView.value) {
      const canvas = sceneView.value.getElementsByTagName("canvas");
      if (!canvas || canvas.length == 0) {
        if (sceneView.value) {
          const cocosContainer = sceneView.value.querySelector(
            "#Cocos3dGameContainer"
          );
          const newCanvas = document.createElement("canvas");
          newCanvas.id = "GameCanvas";
          newCanvas.style.width="100%"
          newCanvas.style.height="100%"
          newCanvas.tabIndex = 99;
          if (cocosContainer) {
            cocosContainer.appendChild(newCanvas);
          }
        }
      }
    }
  }
  reloadEngine();
});

onUnmounted(() => {
  removeEvents();
});

onBeforeUnmount(() => {
  // cocos的实例及dom都不能销毁，该组件销毁的时候
  const element = document.getElementById("GameDiv");
  if (element) {
    element.style.display = "none";
    if (element.parentElement) {
      element.parentElement.removeChild(element);
    }
    document.body.append(element);
  }
});

function addEvents() {
  on(SceneEventType.DeviceSelected, onDeviceSelected);
  on(SceneEventType.DeviceUnselected, onDeviceUnselected);
}
function removeEvents() {
  off(SceneEventType.DeviceSelected, onDeviceSelected);
  off(SceneEventType.DeviceUnselected, onDeviceUnselected);
}
watch(
  () => props.sceneName,
  (newSceneName) => {
    showScene(newSceneName);
  }
);

function onDeviceSelected(e: EventDeviceInfo) {
  emitsAndPostMessage("deviceSelected", e);
}

function onDeviceUnselected(e: EventDeviceInfo) {
  emitsAndPostMessage("deviceUnselected", e);
}

function getSceneManager(scene: cc.Scene | null = null): SceneManager | null {
  if (!scene) {
    scene = engine.value.director.getScene();
  }
  if (scene == null) {
    return null;
  }
  return scene.getComponentInChildren(
    "SceneManager"
  ) as unknown as SceneManager;
}

function showScene(sceneName: string): Promise<cc.Scene> {
  if (!sceneName) {
    sceneName = getDefaultScene().name;
  }
  loadingContent.value = `${sceneName ?? ""}加载中...`;
  isLoading.value = true;
  emitsAndPostMessage("sceneLoading", { sceneName });
  return new Promise<cc.Scene>((resolve, reject) => {
    cc.director.loadScene(
      sceneName,
      (error: null | Error, scene?: cc.Scene) => {
        onLoadScene(error, scene);
        if (error) {
          reject(error);
          isLoading.value = false;

          emitsAndPostMessage("sceneLoadFailed", { sceneName, error });

          return;
        }
        if (!scene) {
          reject(new Error(`${sceneName}不存在`));
          isLoading.value = false;
          emitsAndPostMessage("sceneLoadFailed", {
            sceneName,
            error: new Error(`${sceneName}不存在`),
          });
          return;
        }
        resolve(scene);
        isLoading.value = false;
        emitsAndPostMessage("sceneLoaded", { sceneName, scene });
      }
    );
  });
}

function onLoadScene(error: null | Error, scene?: cc.Scene) {
  if (error || !scene) {
    return;
  }
  if (curSceneInfo.value) {
    curSceneInfo.value.active = false;
  }
  const sceneInfo: SceneInfo | undefined = scenes.find((item) => {
    if (item.name == scene.name) {
      return true;
    }
    return false;
  });
  if (!sceneInfo) {
    throw new Error(`场景${scene.name}未定义`);
  }

  sceneInfo.active = true;
  const sceneMgr = getSceneManager(scene);
  if (sceneMgr) {
    sceneMgr.defaultView = sceneInfo.view ?? defaultView.value;
    curSceneInfo.value = sceneInfo;
  }
}

function reloadCurrentScene() {
  const sceneManager = getSceneManager();
  sceneManager?.restoreView();
}

function switchView() {
  const sceneManager = getSceneManager();
  sceneManager?.switchView();
}

function showDefaultScene() {
  showScene(getDefaultScene().name);
}

function off<EventType extends keyof EventMap>(
  type: EventType,
  callback: EventMap[EventType]
): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  document.removeEventListener(type, callback as any);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  delete callbacks[callback as any];
}

function on<EventType extends keyof EventMap>(
  type: EventType,
  callback: EventMap[EventType]
): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  callbacks[callback as any] = (e: any) => {
    callback(e.detail);
  };
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  document.addEventListener(type, callbacks[callback as any]);
}

async function reloadEngine(): Promise<void> {
  removeEvents();
  addEvents();
  isLoading.value = true;
  loadingContent.value = "脚本加载中...";
  emitsAndPostMessage("sceneLoading", {});
  try {
    await isReady();
  } catch (error) {
    isLoading.value = false;
    emitsAndPostMessage("sceneLoadFailed", new Error("脚本加载失败"));
    throw error;
  }

  if (window["cc"] != null) {
    engine.value = window["cc"];
  } else {
    engine.value = await System.import("cc");
    const settingsPath = combinPath(
      sceneViewerConfig.serverPath,
      sceneViewerConfig.settingsPath
    );
    const assetsServer = combinPath(
      sceneViewerConfig.serverPath,
      sceneViewerConfig.assetsPath
    );
    await cc.game.init({
      // eslint-disable-next-line no-constant-condition
      debugMode: props.debugMode
        ? props.debugMode
        : engine.value.DebugMode.ERROR,
      settingsPath,
      exactFitScreen: true,
      overrideSettings: {
        // assets: {
        //      preloadBundles: [{ bundle: 'main', version: 'xxx' }],
        // }
        profiling: {
          showFPS: props.showFPS,
        },
        assets: {
          server: assetsServer,
          remoteBundles: ["internal", "main"],
        },
		rendering:{
		          effectSettingsPath:assetsServer + '/src/effect.bin'
		        }
      },
    });
    await cc.game.run();
  }

  await showScene(props.sceneName);
}

function emitsAndPostMessage(type: EventTypes, e: any) {
  emits(type, e);
  e = e ?? {};
  const postData: any = {
    type,
    deviceInfo: e.deviceInfo,
    nextDeviceInfo: e.nextDeviceInfo,
  };

  if (curSceneInfo.value) {
    postData.scene = {
      name: curSceneInfo.value.name,
      displayName: curSceneInfo.value.displayName,
      active: curSceneInfo.value.active,
      view: curSceneInfo.value.view,
    };
  }

  if (e.error) {
    postData.error = e.error;
  }

  if (window.parent) {
    window.parent.postMessage(postData, "*");
  }
}

defineExpose({ reloadCurrentScene, switchView, showDefaultScene });
</script>

<style scoped>
.loading {
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 10;
}
</style>
